package widget.drag;

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewGroupCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;

import library.logger.Logger;
import library.utils.DisplayUtil;

/**
 * Created by apple on 2017/4/13.
 */

public class DragLayout extends ViewGroup {

    private DragView mDragView = null;

    private ViewConfiguration configuration;

    private float mPrevMotionY;
    private float mInitialMotionX;
    private float mInitialMotionY;
    private boolean mIsScrollableViewHandlingTouch = false;

    public DragLayout(Context context) {
        super(context);
        _init(context, null);
    }

    public DragLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        _init(context, attrs);
    }

    public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        _init(context, attrs);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public DragLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        _init(context, attrs);
    }

    private void _init(Context context, AttributeSet attrs) {
        configuration = ViewConfiguration.get(context);

        ViewGroupCompat.setMotionEventSplittingEnabled(this, false);

    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode != MeasureSpec.EXACTLY && widthMode != MeasureSpec.AT_MOST) {
            throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
        } else if (heightMode != MeasureSpec.EXACTLY && heightMode != MeasureSpec.AT_MOST) {
            throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
        }

        final int childCount = getChildCount();

        int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
        int layoutWidth = widthSize - getPaddingLeft() - getPaddingRight();

        // First pass. Measure based on child LayoutParams width/height.
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            // We always measure the sliding panel in order to know it's height (needed for show panel)
            if (child.getVisibility() == GONE && i == 0) {
                continue;
            }

            int height = layoutHeight;
            int width = layoutWidth;

            int childWidthSpec;
            if (lp.width == LayoutParams.WRAP_CONTENT) {
                childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST);
            } else if (lp.width == LayoutParams.MATCH_PARENT) {
                childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
            } else {
                childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
            }

            int childHeightSpec;
            if (child instanceof DragView) {
                DragView dragView = (DragView) child;
                height = 0;
                try {
                    height = getBottom() - dragView.getTopLimit();
                } catch (Exception e) {

                }
                childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
            } else if (lp.height == LayoutParams.WRAP_CONTENT) {
                childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
            } else {
                // Modify the height based on the weight.
                if (lp.height != LayoutParams.MATCH_PARENT) {
                    height = lp.height;
                }
                childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
            }

            child.measure(childWidthSpec, childHeightSpec);

        }

        setMeasuredDimension(widthSize, heightSize);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        final int paddingLeft = getPaddingLeft();
        final int paddingTop = getPaddingTop();

        final int childCount = getChildCount();

        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);

            // Always layout the sliding view on the first layout
            if (child.getVisibility() == GONE) {
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            final int childHeight = child.getMeasuredHeight();
            int childTop = paddingTop;

            if (child instanceof DragView) {
                childTop = child.getHeight() != 0 ? child.getTop() : b;
            }

            final int childBottom = childTop + childHeight;
            final int childLeft = paddingLeft;
            final int childRight = childLeft + child.getMeasuredWidth();

            child.layout(childLeft, childTop, childRight, childBottom);
        }

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        try {
            if (mDragView != null) {
                mDragView.getViewDragHelper().processTouchEvent(event);
            }
            return true;
        } catch (Exception ex) {
            // Ignore the pointer out of range exception
            return false;
        }
    }

    private long mLastTouchDownTime;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mIsScrollableViewHandlingTouch && mDragView != null) {
            mDragView.getViewDragHelper().abort();
            return false;
        }

        final int action = MotionEventCompat.getActionMasked(ev);
        boolean interceptForDrag = false;

        switch (action) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_POINTER_DOWN:
                mIsScrollableViewHandlingTouch = false;
                mInitialMotionX = ev.getX();
                mInitialMotionY = ev.getY();
                if (mDragView != null) {
                    interceptForDrag |= mDragView.getViewDragHelper().shouldInterceptTouchEvent(ev);
                }

                if (action == MotionEvent.ACTION_DOWN && mDragView != null && mDragView.getViewDragHelper().getCapturedView() == null
                        && mDragView.isAvailable() && mDragView.isAllowTouchOutSide()) {
                    final long downTime = ev.getEventTime();
                    final long interval = downTime - mLastTouchDownTime;
                    mLastTouchDownTime = downTime;
                    if (interval > 0 && interval < configuration.getScaledDoubleTapSlop()) {
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (mDragView != null) {
                    interceptForDrag |= mDragView.getViewDragHelper().shouldInterceptTouchEvent(ev);
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (mDragView != null)
                    mDragView.getViewDragHelper().processTouchEvent(ev);
                return false;
        }
        return interceptForDrag;
    }

    private boolean mIsScrollerViewOnMove = false;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final int action = MotionEventCompat.getActionMasked(ev);
        final float x = ev.getX();
        final float y = ev.getY();

        if (mDragView != null && mDragView.getViewDragHelper().getCapturedView() == null
                && mDragView.isAvailable() && mDragView.isAllowTouchOutSide()) {
            if (!isViewUnder(mDragView, ev.getX(), ev.getY())) {
                if (action == MotionEvent.ACTION_DOWN) {
                } else if (action == MotionEvent.ACTION_MOVE) {
                    float distanceX = Math.abs(ev.getX() - mInitialMotionX);
                    float distanceY = Math.abs(ev.getY() - mInitialMotionY);
                    double distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
                    if (distance > configuration.getScaledTouchSlop()) {
                        mDragView.getViewDragHelper().captureChildView(mDragView, ev.getPointerId(0));
                    } else
                        return false;
                }
            }
        }


        if (action == MotionEvent.ACTION_DOWN) {
            if (mDragView != null && mDragView.isAvailable() && isViewUnder(mDragView, x, y)) {
                mDragView.getViewDragHelper().abort();
            }
            mIsScrollerViewOnMove = false;
            mIsScrollableViewHandlingTouch = false;
            mPrevMotionY = y;
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                if (child instanceof DragView) {
                    DragView view = (DragView) child;
                    ViewDragHelper helper = view.getViewDragHelper();
                    if (helper == null)
                        continue;
                    if (view.isAvailable()) {
                        mDragView = view;
                        break;
                    }
                }
            }
        } else if (action == MotionEvent.ACTION_MOVE) {
            float dy = y - mPrevMotionY;
            mPrevMotionY = y;
            if (mDragView == null) {
                return super.dispatchTouchEvent(ev);
            }
            // If the scroll view isn't under the touch, pass the
            // event along to the dragView.
            View scrollerView = mDragView.getVisibleScrollerView();
            if (!isViewUnder(scrollerView, (int) mInitialMotionX, (int) mInitialMotionY)) {
                return super.dispatchTouchEvent(ev);
            }

            // Which direction (up or down) is the drag moving?
            if (dy > 0) { // Collapsing
                // Is the child less than fully scrolled?
                // Then let the child handle it.
                if (DragUtils.canScroll(scrollerView, -1)) {
                    mIsScrollableViewHandlingTouch = true;
                    return super.dispatchTouchEvent(ev);
                }

                // Was the child handling the touch previously?
                // Then we need to rejigger things so that the
                // drag panel gets a proper down event.
                if (mIsScrollableViewHandlingTouch && !mIsScrollerViewOnMove) {
                    // Send an 'UP' event to the child.
                    MotionEvent up = MotionEvent.obtain(ev);
                    up.setAction(MotionEvent.ACTION_CANCEL);
                    super.dispatchTouchEvent(up);
                    up.recycle();

                    // Send a 'DOWN' event to the panel. (We'll cheat
                    // and hijack this one)
                    ev.setAction(MotionEvent.ACTION_DOWN);
                }

                mIsScrollableViewHandlingTouch = false;
                return super.dispatchTouchEvent(ev);
            } else if (dy < 0) { // Expanding
                // Is the panel less than fully expanded?
                // Then we'll handle the drag here.
                if (mDragView.getTop() > mDragView.getTopLimit()) {
                    mIsScrollableViewHandlingTouch = false;
                    return super.dispatchTouchEvent(ev);
                }

                // Was the panel handling the touch previously?
                // Then we need to rejigger things so that the
                // child gets a proper down event.
                if (!mIsScrollableViewHandlingTouch && mDragView.getViewDragHelper().getCapturedView() != null) {
                    mDragView.getViewDragHelper().cancel();
                    ev.setAction(MotionEvent.ACTION_DOWN);
                }

                mIsScrollerViewOnMove = true;
                mIsScrollableViewHandlingTouch = true;
                return super.dispatchTouchEvent(ev);
            }
        } else if (action == MotionEvent.ACTION_UP) {
            // If the scrollable view was handling the touch and we receive an up
            // we want to clear any previous dragging state so we don't intercept a touch stream accidentally
            if (mIsScrollableViewHandlingTouch && mDragView != null) {

            }
        }

        // In all other cases, just let the default behavior take over.
        return super.dispatchTouchEvent(ev);
    }


    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        if (child instanceof DragView) {
            DragView view = (DragView) child;
            if (view.getTop() < getBottom()) {
                int h = DisplayUtil.dp2px(3);
                int offset = view.getTop() - view.getTopLimit();
                Rect rect = new Rect(
                        view.getLeft(),
                        view.getTop() - Math.min(offset, h),
                        view.getRight(),
                        view.getTop());
                Drawable drawable = getGradientDrawable();
                drawable.setBounds(rect);
                drawable.draw(canvas);
            }
        }
        return super.drawChild(canvas, child, drawingTime);
    }

    @Override
    public void computeScroll() {
        super.computeScroll();

        boolean continueSettling = false;
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child instanceof DragView) {
                DragView view = (DragView) child;
                ViewDragHelper helper = view.getViewDragHelper();
                if (helper == null)
                    continue;
                if (helper.continueSettling(true)) {
                    continueSettling = true;
                }
            }
        }
        if (continueSettling)
            ViewCompat.postInvalidateOnAnimation(this);

    }

    private boolean isViewUnder(View view, float x, float y) {
        if (view == null) return false;
        int[] viewLocation = new int[2];
        view.getLocationOnScreen(viewLocation);
        int[] parentLocation = new int[2];
        this.getLocationOnScreen(parentLocation);
        float screenX = parentLocation[0] + x;
        float screenY = parentLocation[1] + y;
        return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth() &&
                screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight();
    }

    private Drawable getGradientDrawable() {
        final int deep = 0xFF / 4;
        final int split = 3;
        final int[] colors = new int[split + 1];
        final int diff = deep / (split);
        for (int i = 0; i < split; i++) {
            colors[i] = Color.argb(deep - diff * i, 0, 0, 0);
        }
        colors[split] = Color.TRANSPARENT;
        return new GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, colors);
    }

    public void closeAnyOther(View except) {
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child != except && child instanceof DragView) {
                DragView view = (DragView) child;
                if (view.isAvailable() && view.isShowing()) {
                    view.pause();
                }
            }
        }
        ViewCompat.postInvalidateOnAnimation(this);
    }

    private void debug(String fmt, Object... objs) {
        Logger.simple(fmt, objs);
    }


}
